#include "PrecompiledHeader_Reborn.h"
#include "Nebula.h"

/*--------------------------------------------------------------------------*/

Nebula::Nebula(const NodeCreation& nc, const NebulaCreation& nec) :
	IRenderableObject(nc)
{
	RenderTaskDesc desc;
	desc.m_material = NULL;
	desc.m_argument = 10;
	desc.m_flags = ERenderTaskFlag::NoMaterial;
	desc.m_priority = 0;
	desc.m_object = this;
	this->AddRenderTask(desc);

	this->m_params = nec;
	this->m_nebulaMaterial = g_Material->GetMaterial("mat_nebula");
	this->m_nebulaVao = NULL;

	srand((uint) this);

	uint type = nec.m_type;

	if (type == ENebulaType::Random)
	{
		type = rand() % ENebulaType::Random;
	}

	switch (type)
	{
	case ENebulaType::Spiral:
	{
		this->PrepareSpiralNebula();
		break;
	}

	case ENebulaType::Chaos:
	{
		this->PrepareChaosNebula();
		break;
	}


	}

	this->SetOcclusionTestEnabled(false);
}

/*--------------------------------------------------------------------------*/

Nebula::~Nebula()
{
	ROpenGL::DeleteVertexArray(&this->m_nebulaVao);
}

/*--------------------------------------------------------------------------*/

void Nebula::RenderableBinder(uint treeCalcFlags)
{
	this->BindRenderTask(this->GetSceneInstance()->GetRenderPhase(), 10);
}

/*--------------------------------------------------------------------------*/

void Nebula::RenderableHelper(const uint i, ERenderQuality::Type quality, uint pendingInstances)
{
	if (i != 10)
	{
		return;
	}

#ifdef VL_RENDER_STATISTICS
	g_Hud->GetStatisticsManager()->IncrementStatistic("Nebula");
#endif

	/*--------------------------------------------------------------------------*/

	g_Material->BindMaterial(this->m_nebulaMaterial, EMaterialTechnique::Default);

	ROpenGL::SetUniformBufferValue(g_Renderer->GetUniformBuffer(EUbo::Commons), EUboCommons::Var_0, &this->m_params.m_color);

	ROpenGL::BindVertexArray(this->m_nebulaVao);
	ROpenGL::DrawVertexArray(0);

// 	g_Renderer->DrawLine3D(glm::vec3(-10000.0f, 0.0f, 0.0f), glm::vec3(10000.0f, 0.0f, 0.0f), NULL, Color::Red);
// 	g_Renderer->DrawLine3D(glm::vec3(0.0f, -10000.0f, 0.0f), glm::vec3(0.0f, 10000.0f, 0.0f), NULL, Color::Green);
// 	g_Renderer->DrawLine3D(glm::vec3(0.0f, 0.0f, -10000.0f), glm::vec3(0.0f, 0.0f, 10000.0f), NULL, Color::Blue);
}

/*--------------------------------------------------------------------------*/

void Nebula::PrepareSpiralNebula()
{
	AssertLogC(this->m_nebulaVao != NULL, "Overwriting existing nebula!");

	std::default_random_engine gen((uint) this);

	std::normal_distribution<float> distX(this->m_params.m_center.x, this->m_params.m_deviation.x * 0.5f);
	std::normal_distribution<float> distY(this->m_params.m_center.y, this->m_params.m_deviation.y * 0.5f);
	std::normal_distribution<float> distZ(this->m_params.m_center.z, this->m_params.m_deviation.z * 0.5f);

	/*--------------------------------------------------------------------------*/

	std::stringstream nebname;
	nebname << "Nebula_" << this->GetName() << "_" << this << "_";
	std::string name = nebname.str();

	this->m_nebulaVao = ROpenGL::GenVertexArray(name + "VAO",
	                                            GL_QUADS,
	                                            this->m_params.m_numQuads * 4);

	GLVertexBuffer* vert = ROpenGL::GenBuffer(name + "VB", EVertexDeclaration::Position0, this->m_params.m_numQuads * 4);
	GLVertexBuffer* texc = ROpenGL::GenBuffer(name + "TC", EVertexDeclaration::Texcoord0, this->m_params.m_numQuads * 4);
	GLVertexBuffer* norm = ROpenGL::GenBuffer(name + "NM", EVertexDeclaration::Normal0, this->m_params.m_numQuads * 4);

	/*--------------------------------------------------------------------------*/

	glm::vec3* vv = NULL;
	glm::vec2* vt = NULL;
	glm::vec3* vn = NULL;

	vv = (glm::vec3*) ROpenGL::GetShadowBuffer(vert,
	                                           this->m_params.m_numQuads * 4 * sizeof(glm::vec3),
	                                           0);

	vt = (glm::vec2*) ROpenGL::GetShadowBuffer(texc,
	                                           this->m_params.m_numQuads * 4 * sizeof(glm::vec2),
	                                           0);

	vn = (glm::vec3*) ROpenGL::GetShadowBuffer(norm,
	                                           this->m_params.m_numQuads * 4 * sizeof(glm::vec3),
	                                           0);

	/*--------------------------------------------------------------------------*/

	float maxdist = 0.0f;

	for (uint i = 0; i < this->m_params.m_numQuads * 4; i += 4)
	{
		glm::vec3 center = glm::vec3(distX(gen),
		                             distY(gen),
		                             distZ(gen));

		glm::vec3 a			= glm::normalize(glm::vec3(RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f)));
		glm::vec3 helper	= glm::normalize(glm::vec3(RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f)));
		glm::vec3 b			= glm::normalize(glm::cross(a, helper));

		a *= this->m_params.m_quadradius;
		b *= this->m_params.m_quadradius;

		vv[i + 0] = center - a - b;
		vv[i + 1] = center + a - b;
		vv[i + 2] = center + a + b;
		vv[i + 3] = center - a + b;

		for (uint j = 0; j < 4; ++j)
		{
			maxdist = glm::max(maxdist, glm::abs(vv[i + j].x));
			maxdist = glm::max(maxdist, glm::abs(vv[i + j].y));
			maxdist = glm::max(maxdist, glm::abs(vv[i + j].z));
		}

		vt[i + 0] = glm::vec2(0.0f, 0.0f);
		vt[i + 1] = glm::vec2(1.0f, 0.0f);
		vt[i + 2] = glm::vec2(1.0f, 1.0f);
		vt[i + 3] = glm::vec2(0.0f, 1.0f);

		helper = glm::normalize(glm::cross(a, b));

		vn[i + 0] = helper;
		vn[i + 1] = helper;
		vn[i + 2] = helper;
		vn[i + 3] = helper;

		i += 4;
	}

	this->SetRadius(maxdist);
	this->SetScale(1.0f);

	ROpenGL::FlushShadowBuffer(texc);
	ROpenGL::FlushShadowBuffer(vert);
	ROpenGL::FlushShadowBuffer(norm);

	ROpenGL::BindVertexArray(this->m_nebulaVao);
	ROpenGL::AttachBuffer(vert);
	ROpenGL::AttachBuffer(norm);
	ROpenGL::AttachBuffer(texc);

	/*--------------------------------------------------------------------------*/

	ROpenGL::UniqueBufferOwner(this->m_nebulaVao, vert);
	ROpenGL::UniqueBufferOwner(this->m_nebulaVao, norm);
	ROpenGL::UniqueBufferOwner(this->m_nebulaVao, texc);
}

/*--------------------------------------------------------------------------*/

void Nebula::PrepareChaosNebula()
{
	AssertLogC(this->m_nebulaVao != NULL, "Overwriting existing nebula!");

	std::default_random_engine gen((uint) this);

	std::normal_distribution<float> distX(this->m_params.m_center.x, this->m_params.m_deviation.x);
	std::normal_distribution<float> distY(this->m_params.m_center.y, this->m_params.m_deviation.y);
	std::normal_distribution<float> distZ(this->m_params.m_center.z, this->m_params.m_deviation.z);

	/*--------------------------------------------------------------------------*/

	std::stringstream nebname;
	nebname << "Nebula_" << this->GetName() << "_" << this << "_";
	std::string name = nebname.str();

	this->m_nebulaVao = ROpenGL::GenVertexArray(name + "VAO",
	                                            GL_QUADS,
	                                            this->m_params.m_numQuads * 4);

	GLVertexBuffer* vert = ROpenGL::GenBuffer(name + "VB", EVertexDeclaration::Position0, this->m_params.m_numQuads * 4);
	GLVertexBuffer* texc = ROpenGL::GenBuffer(name + "TC", EVertexDeclaration::Texcoord0, this->m_params.m_numQuads * 4);
	GLVertexBuffer* norm = ROpenGL::GenBuffer(name + "NM", EVertexDeclaration::Normal0, this->m_params.m_numQuads * 4);

	/*--------------------------------------------------------------------------*/

	glm::vec3* vv = NULL;
	glm::vec2* vt = NULL;
	glm::vec3* vn = NULL;

	vv = (glm::vec3*) ROpenGL::GetShadowBuffer(vert,
	                                           this->m_params.m_numQuads * 4 * sizeof(glm::vec3),
	                                           0);

	vt = (glm::vec2*) ROpenGL::GetShadowBuffer(texc,
	                                           this->m_params.m_numQuads * 4 * sizeof(glm::vec2),
	                                           0);

	vn = (glm::vec3*) ROpenGL::GetShadowBuffer(norm,
	                                           this->m_params.m_numQuads * 4 * sizeof(glm::vec3),
	                                           0);

	/*--------------------------------------------------------------------------*/

	float maxdist = 0.0f;

	glm::vec3 center = glm::vec3(distX(gen),
	                             distY(gen),
	                             distZ(gen));

	//glm::vec3 centerSum = glm::vec3();

	glm::vec3 direction = glm::normalize(glm::vec3(RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f)));

	for (uint i = 0; i < this->m_params.m_numQuads * 4; i += 4)
	{
		glm::vec3 moddir = glm::normalize(glm::vec3(RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f)));
		direction = glm::normalize(glm::vec3(direction * 0.6f + moddir * 0.4f)) * 0.5f;

		center += direction * this->m_params.m_quadradius;
		//centerSum += center;

		glm::vec3 a			= glm::normalize(glm::vec3(RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f)));
		glm::vec3 helper	= glm::normalize(glm::vec3(RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f), RandFloat(-1.0f, 1.0f)));
		glm::vec3 b			= glm::normalize(glm::cross(a, helper));

		a *= this->m_params.m_quadradius;
		b *= this->m_params.m_quadradius;

		vv[i + 0] = center - a - b;
		vv[i + 1] = center + a - b;
		vv[i + 2] = center + a + b;
		vv[i + 3] = center - a + b;

		for (uint j = 0; j < 4; ++j)
		{
			maxdist = glm::max(maxdist, glm::abs(vv[i + j].x));
			maxdist = glm::max(maxdist, glm::abs(vv[i + j].y));
			maxdist = glm::max(maxdist, glm::abs(vv[i + j].z));
		}

		vt[i + 0] = glm::vec2(0.0f, 0.0f);
		vt[i + 1] = glm::vec2(1.0f, 0.0f);
		vt[i + 2] = glm::vec2(1.0f, 1.0f);
		vt[i + 3] = glm::vec2(0.0f, 1.0f);

		helper = glm::normalize(glm::cross(a, b));

		vn[i + 0] = helper;
		vn[i + 1] = helper;
		vn[i + 2] = helper;
		vn[i + 3] = helper;

		i += 4;
	}

	this->SetRadius(maxdist);
	this->SetScale(1.0f);

	ROpenGL::FlushShadowBuffer(texc);
	ROpenGL::FlushShadowBuffer(vert);
	ROpenGL::FlushShadowBuffer(norm);

	ROpenGL::BindVertexArray(this->m_nebulaVao);
	ROpenGL::AttachBuffer(vert);
	ROpenGL::AttachBuffer(norm);
	ROpenGL::AttachBuffer(texc);

	/*--------------------------------------------------------------------------*/

	ROpenGL::UniqueBufferOwner(this->m_nebulaVao, vert);
	ROpenGL::UniqueBufferOwner(this->m_nebulaVao, norm);
	ROpenGL::UniqueBufferOwner(this->m_nebulaVao, texc);
}

/*--------------------------------------------------------------------------*/
